#include <iostream>
#include <sys/socket.h>
#include <sys/select.h>//select()
#include <cstdlib>//exit(), EXIT_FAILURE/SUCCESS
#include <stdio.h>//fprintf, stderr
#include <string.h>//strlen
#include <pwd.h>//pwuid
#include <vector>//vector
#include <signal.h>//signaux
#include <sstream>//ostringstream
#include "sock.h"
#include "sockdist.h"

#define MAX_CONNEX 5

int descpr;

using namespace std;


void kill_p(int sig){
	exit(EXIT_SUCCESS);
}

void do_any(int sig){
}

void quit(int sig){
	char quit[]="quit";
	send(descpr, quit, 5, 0);
	exit(EXIT_FAILURE);
}

void serveur(){
	int descpub;
	int lenF;
	char message[200];
	string msgEnv;
	int rep, env;
	char nom_c[20];
	string msg_nom_c;

	//si on recoit un signal SIGPIPE(lors de la deco d'un client) on fait do_any(int) : rien
	struct sigaction sig_pipe;
	sig_pipe.sa_handler=do_any;
	sigaction(SIGPIPE, &sig_pipe, NULL);

	sockaddr_in brCv;
	socklen_t lenCv;
	int descCv=-1;

	Sock brPub(SOCK_STREAM, (short)21345, 0);
	if(brPub.good()){
		descpub=brPub.getsDesc();
	}
	else{
		cout<<"Port encore en cours d'utilisation"<<endl;
		exit(EXIT_FAILURE);
	}
	
	//on se met en attente de connexion (MAX_CONNEX = maximum de connexions possible)
	lenF=listen(descpub,MAX_CONNEX);
	lenCv=sizeof(struct sockaddr_in);
	
	//declaration table des fd
	fd_set desc_lect;
	//declaration tableau contenant les fd qui nous interessent
	vector<int> tab_fd(1,descpub);
	vector<string> tab_nom(1, "null");
	vector<bool> tab_connected(1, false);
	int move=0;
	int nb_co;
	string nb_co_c;
	string liste_co;
	ostringstream out;

	while(1){
		//reinitialise a 0 le nb de connectes au serveur
		nb_co=0;
		//reinitialise a la chaine vide le stringstream out
		out.str("");
		//reinitialise a vide le nom du client
		for (int i=0; i<20; i++){
			nom_c[i]=' ';
		}
		//reinitialise le message nom client, le message a envoyer et la liste des co a vide
		msg_nom_c = "";
		msgEnv="";
		liste_co="";
		//reinitialise a vrai les fd
		FD_ZERO(&desc_lect);
		for (int i=0; i<tab_fd.size();i++){
			FD_SET(tab_fd[i],&desc_lect);
		}

		move = select(30, &desc_lect, NULL, NULL, NULL);

		if (move <0){
			perror("select()");
			exit(EXIT_FAILURE);
		}

		else if (move == 0){
			perror("Timeout expire");
			exit(EXIT_FAILURE);
		}

		else {
			//si le fd modifie est "celui de la demande de connexion"
			if (FD_ISSET(tab_fd[0],&desc_lect)){
					//on accepte la connexion distante
					descCv=accept(descpub, (struct sockaddr *)&brCv, &lenCv);
					cout<<endl<<"Connexion etablie avec le client "<<inet_ntoa(brCv.sin_addr)<<endl;
					//incrementation tab_fd
					tab_fd.push_back(descCv);
					tab_connected.push_back(false);
			}
				
			else {
				for (int i=1; i<tab_fd.size();i++){
					if (FD_ISSET(tab_fd[i],&desc_lect)){
						cout<<"Message reçu"<<endl;
						//on recoit le message du client
						rep=recv(tab_fd[i], message, 200,0);
						
						//si le message commence par "/name", on envoie le nom du client a tous
						if (message[0]=='/' && message[1]=='n' && message[2]=='a' && message[3]=='m' && message[4]=='e' && message[5]==' '){
							//si le client envoie un message "/name ..." pour changer son pseudo
							if (tab_connected[i]){
								msg_nom_c = "\n* ";
								msg_nom_c += tab_nom[i];
								for (int j=0; j<20;j++){
									nom_c[j]=message[j+6];
								}
								msg_nom_c +=" vient de changer son pseudo en : ";
								msg_nom_c += nom_c;
								for (int j=1; j<tab_fd.size();j++){
									env=send(tab_fd[j], msg_nom_c.c_str(), msg_nom_c.size(),0);
								}
								tab_nom[i]=nom_c;
							}
							//sinon si le client se connecte au serveur
							else {
								tab_connected[i]=true;
								for (int j=0; j<20;j++){
									nom_c[j]=message[j+6];
								}
								
								msg_nom_c = "\n* ";
								msg_nom_c += nom_c;
								msg_nom_c +=" vient de se connecter";
								for (int j=1; j<tab_fd.size();j++){
									//on envoie a tous, sauf au client considere
									if (j != i){
										env=send(tab_fd[j], msg_nom_c.c_str(), msg_nom_c.size(),0);
									}
										nb_co++;
								}

								tab_nom.push_back(nom_c);
								
								//s'il y au moins 1 autre personne en ligne
								if (nb_co >1){
									//envoi de la liste des connectes au client qui se connecte au serveur
									liste_co ="\n\nListe des personnes en ligne\n";
									env=send(tab_fd[i], liste_co.c_str(), liste_co.size(),0);

									for (int j=1; j < tab_fd.size(); j++){
										//on liste tous les clients, sauf le client considere
										if (j != i){
											liste_co = tab_nom[j] + " est en ligne\n";
											env=send(tab_fd[i], liste_co.c_str(), liste_co.size(),0);
										}
									}
								}
								//sinon, s'il n'y a personne en ligne
								else {
									liste_co = "\n\nIl n'y a personne en ligne\n";
									env=send(tab_fd[i], liste_co.c_str(), liste_co.size(),0);
								}

							}
						}

						//si on recoit le message "quit" du client
						else if (!strcmp(message,"quit")){
							//on retire le fd qui lui est associe de la table des fd a surveiller
							tab_fd.erase(tab_fd.begin()+i);
						//	close(tab_fd[i]);

							//on envoie un message a tous les autres clients indiquant qu'un client s'est deco
							for (int j=1; j<tab_fd.size();j++){
								nb_co++;
							}
							out<<nb_co;
							if (nb_co <=1){
								nb_co_c ="\n* "+ tab_nom[i]+" s'est déconnecté ("+out.str()+" personne en ligne)";
							}
							else {
								nb_co_c ="\n* "+ tab_nom[i]+" s'est déconnecté ("+out.str()+" personnes en ligne)";
							}
							cout<<nb_co_c<<endl;
							for (int j=1; j<tab_fd.size();j++){
								env=send(tab_fd[j], nb_co_c.c_str(),nb_co_c.size(),0);
							}
							tab_nom.erase(tab_nom.begin()+i);
							tab_connected.erase(tab_connected.begin()+i);
						}
						
						//on envoie le message a tous les clients y compris celui qui l'a envoye
						else {
							msgEnv =  tab_nom[i]+ " : ";
							msgEnv += message;
							for (int j=1; j<tab_fd.size();j++){
								env=send(tab_fd[j], msgEnv.c_str(), msgEnv.size(),0);
							}
						}
					}
				}
			}
		}
	}
}

void client(char* hote){
	int lenPub;
	int connex;
	char msgEnv[200];
	char msgRecept[200];
	int env,rep;
	uid_t uid=getuid();

	//declaration du nom de client
	struct passwd *id;
	id=getpwuid(uid);
	char idC[30]="";
	strcat(idC,"/name ");
	strcat(idC, id->pw_name);

	//si on recoit le signal du fils du processus, on quitte le prog
	struct sigaction sig_f;
	sig_f.sa_handler=kill_p;
	sigaction(SIGCHLD, &sig_f, NULL);

	//si on recoit le signal du depart brutal du client (processus tue brutalement "kill -9 PID" ou ^C)
	struct sigaction quit_c;
	quit_c.sa_handler=quit;
	sigaction(SIGINT, &quit_c, NULL);

	sockaddr_in *adrPub;

	Sock brPr(SOCK_STREAM, 0);
	if (brPr.good()){
		descpr=brPr.getsDesc();
	}
	else{
		cout<<"Probleme avec la BR privee du client"<<endl;
		exit(EXIT_FAILURE);
	}

	SockDist brDist(hote, (short)21345);
	adrPub=brDist.getAdrDist();
	
	lenPub=sizeof(struct sockaddr_in);
	
	//demande de connexion a l'hote
	connex=connect(descpr,(struct sockaddr *)adrPub, lenPub);
	//Si connect=0, la connexion avec le serveur est etablie, sinon, probleme avec le serveur
	if (connex==0){
		cout<<endl<<"Connexion avec le serveur "<<inet_ntoa(adrPub->sin_addr)<<" etablie."<<endl;
	}
	else {
		fprintf(stderr,"\nErreur lors de la connexion : le serveur %s ne repond pas.\n", inet_ntoa(adrPub->sin_addr));
		exit(EXIT_FAILURE);
	}
	
	//envoi de l'uid au serveur
	env=send(descpr,idC, 20,0);
	
	pid_t msgServ;
	bool first_msg=false;
	bool forked=false;

	while(strcmp(msgEnv,"quit")){
		
		if (forked== false){
			msgServ = fork(); 
			forked=true;
		}

		if (msgServ==0){
			//reception et affichage du message du serveur
			rep=recv(descpr, msgRecept, 200, 0);
			if (rep==0){
				fprintf(stdout,"\nErreur : le serveur s'est deconnecté.\n");
				//mort du fils. Envoie un signal au pere pour se suicider
				exit(EXIT_SUCCESS);
			}
			msgRecept[rep]='\0';
			cout<<msgRecept<<endl;
			
		//	exit(0);
		}

		else{
			if (first_msg==false){
				cin.getline(msgEnv,0);
				first_msg=true;
			}
			cin.getline(msgEnv,200);
			env=send(descpr,msgEnv, 200,0);
			if (env == 0){
				fprintf(stderr, "Le message n'a pas été correctement envoyé.\n");
			}
		}
	}
}

int main(int argc, char** argv){
	string rep1;
	char rep2[10];
	cout<<"client ou serveur ?"<<endl;
	cin>>rep1;

	if (rep1=="client"){
		cout<<"client"<<endl;
		cout<<"Sur quelle machine voulez-vous vous connecter?"<<endl;
		cin>>rep2;
		client(rep2);
	}
	else if (rep1=="serveur"){
		cout<<"serveur"<<endl;
		serveur();
	}
	else {
		fprintf(stderr,"Erreur \"client ou serveur ?\" : reponse incorrecte\n");
		return EXIT_FAILURE;
	}

	return 0;
}
